Skip to content

fix(parser): parenthesized type as an arrow return type (#62) - #65

Merged
ericsssan merged 2 commits into
mainfrom
fix/62-paren-arrow-return-type
Jun 25, 2026
Merged

fix(parser): parenthesized type as an arrow return type (#62)#65
ericsssan merged 2 commits into
mainfrom
fix/62-paren-arrow-return-type

Conversation

@ericsssan

Copy link
Copy Markdown
Owner

Fixes #62.

Problem

looksLikeFunctionTypeParams ended with a "scan to the matching ), then check for =>" heuristic that classified any ( … ) => as a function type, ignoring the content. So an arrow whose return type is a parenthesized type

const f = (): (void) => {};   // whole arrow became an ErrorNode

— was read as the function type (void) => {}, swallowing the arrow's own =>. (void), (T | U), (readonly T[]), etc. all hit this. (Parenthesized param types and bare return types already worked.)

Fix

Replace the scan-forward with TS's isUnambiguouslyStartOfFunctionType logic: skip an optional parameter-property modifier and exactly one parameter binding (identifier / this / destructuring pattern — not a reserved bare-type keyword like void/null/true/false/this, and not a leading (), then require a parameter separator (: ? = ,) or ) immediately followed by =>. A type operator after the first identifier (T | U, T[]) therefore falls through to a parenthesized type, leaving the => for the enclosing arrow.

Validation

  • New parser tests: parenthesized return types ((void), (T | U), (readonly T[]), nested, object-shorthand, js_ts) parse as arrows (no ErrorNode); the return type is a TSParenthesizedType; genuine function types still parse as TSFunctionType (including (public B) => modifiers and ({}?: T) => optional patterns); a bare (void) stays a parenthesized type.
  • TS conformance back at baseline 17910/17913 · 1210/1223. (An initial over-strict version regressed 5 parameter-list cases — (public B) => and ({}?: T) => — now covered by the modifier-skip and the ? separator.)
  • Babel 1928/1928 · 1548/1548; test262 3966/3966 · 1389/1389.
  • Semantic sweep over 19,233 files: 0 crashes, only positive deltas (+8 scopes / +37 symbols / +28 refs — arrows recovered from ErrorNodes), diagnostics unchanged.

`looksLikeFunctionTypeParams` ended with a "scan to the matching `)` then check
for `=>`" heuristic that classified ANY `( … ) =>` as a function type, ignoring
the content. So an arrow return type that is a parenthesized type —
`const f = (): (void) => {}` — was read as the function type `(void) => {}`,
swallowing the arrow's own `=>`; the whole arrow then became an ErrorNode.
`(void)`, `(T | U)`, `(readonly T[])` etc. all hit this.

Replace the scan-forward with TS's isUnambiguouslyStartOfFunctionType logic:
skip an optional parameter-property modifier and exactly ONE parameter binding
(identifier / `this` / destructuring pattern — NOT a reserved bare-type keyword
like `void`/`null`/`true`/`false`/`this`, and NOT a leading `(`), then require a
parameter separator (`:` `?` `=` `,`) or `)` immediately followed by `=>`. A
type operator after the first identifier (`T | U`, `T[]`) therefore correctly
falls through to a parenthesized type, leaving the `=>` for the enclosing arrow.

Validated: full suite green; TS conformance back at baseline 17910/17913 ·
1210/1223 (an initial over-strict version regressed 5 parameter-list/function-
type cases — `(public B) =>` modifiers and `({}?: T) =>` optional patterns —
now covered by the modifier-skip and the `?` separator); babel 1928/1928 ·
1548/1548; test262 3966/3966 · 1389/1389; semantic sweep 0 crashes with only
positive deltas (+8 scopes / +37 symbols / +28 refs — arrows recovered from
ErrorNodes), diagnostics unchanged.
…s)`, tests

Three review findings on the looksLikeFunctionTypeParams rewrite:

1. Scope-event leak (regression review). Classifying expression-shaped parens
   like `(b = 1)` as function-type params makes the conditional-consequent
   typed-arrow speculation in parseParenthesized run parseFunctionType, which
   emits scope_open/declare events. The backtrack restored tok/nodes/extra but
   NOT the event stream, leaking a phantom function scope + parameter (wrong
   reference data for `c ? x : (b = 1) && b`). Add Parser.resetEventsTo and
   truncate events on both backtrack paths.

2. Memory leak: that same speculation also allocPrints a diagnostic (no `=>`
   after the fake params); the backtrack's shrinkRetainingCapacity dropped it
   without freeing the message. Add Parser.truncateDiagnostics (frees discarded
   messages, matching the tree's deinit) and use it on the backtrack paths.

3. `(this) =>` fidelity (correctness review): a bare `this` parameter is a valid
   function type in TS, so accept `.kw_this` as a parameter binding instead of
   treating `(this)` as a parenthesized type.

Tests: pin the arrow's actual ArrowData.return_type (not a loose whole-tree
hasNodeTag scan); add union/intersection/conditional return-type cases; add a
semantic regression test that the conditional-with-paren-alternate leaks no
phantom scope or duplicate parameter.

Validated: full suite green (no leaks under the testing allocator); TS
conformance 17910/17913 · 1210/1223; babel 1928/1928 · 1548/1548; test262
3966/3966 · 1389/1389.
@ericsssan
ericsssan merged commit 7719fc1 into main Jun 25, 2026
2 checks passed
@ericsssan
ericsssan deleted the fix/62-paren-arrow-return-type branch June 25, 2026 06:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

parser: parenthesized type as arrow return type fails (whole arrow becomes ErrorNode)

1 participant